﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace mkv16
{
    public class Brain
    {
        private const String MY_NAME = "mkv16";
        private const String DOMAIN_PRIVATE = "private";

        protected IProtocol irc1;
        protected IProtocol irc2;
        protected IProtocol console;

        protected Fact result;
        protected MemoryStore publicMemory;
        protected MemoryStore privateMemory;
        protected MemoryStore dreamMemory;

        public Brain()
        {
            CreateObjects();
            RegisterProtocolListeners(irc1);
            RegisterProtocolListeners(irc2);
            RegisterProtocolListeners(console);

            irc1.Connect("irc.newnet.net", MY_NAME);
            irc2.Connect("irc.quakenet.org", MY_NAME);
            console.Connect("local", MY_NAME);
        }

        public void CreateObjects()
        {
            irc1 = new IRCProtocol();
            irc2 = new IRCProtocol();
            console = new ConsoleProtocol();

            publicMemory = new MemoryStore("memory/Public.memory");
            privateMemory = new MemoryStore("memory/Private.memory");
            dreamMemory = new MemoryStore("memory/Dreams.memory");
        }

        public void RegisterProtocolListeners(IProtocol protocol)
        {
            protocol.ConnectionFail += OnConnectionFail;
            protocol.ConnectionWarning += OnConnectionWarning;
            protocol.ConnectionSuccess += OnConnectionSuccess;

            protocol.MessageReceived += OnMessageReceived;
            protocol.MessageSendFail += OnMessageFail;
            protocol.MessageSendSuccess += OnMessageSent;
        }

        public void ActionFailed(string action, string text)
        {
            Console.WriteLine(String.Format("Action {0} failed: {1}", action, text));
        }

        public void OnMessageReceived(IProtocol context, Message message)
        {
            try
            {
                ScanMessage(context, message);

                if (message.Domain == DOMAIN_PRIVATE)
                {
                    CheckForActions(context, message, result);
                }
            }
            catch
            {
                Console.WriteLine(String.Format("Failed to process message from {0}: {1}", message.Sender, message.Text));
            }
            
        }

        public void OnMessageSent(IProtocol context, Message message)
        {
            Console.WriteLine("Message sent: " + message.ToString());
        }

        public void OnMessageFail(IProtocol context, Message message)
        {
            Console.WriteLine("Send message failed: " + message.ToString());
        }

        public void OnConnectionSuccess(IProtocol context, string report)
        {
            Console.WriteLine(String.Format("Connection success: {0}, {1}", context.ID, report));
        }

        public void OnConnectionWarning(IProtocol context, string report)
        {
            Console.WriteLine(String.Format("Connection warning: {0}, {1}", context.ID, report));
        }

        public void OnConnectionFail(IProtocol context, string report)
        {
            Console.WriteLine(String.Format("Connection failed: {0}, {1}", context.ID, report));
        }

        protected void ScanMessage(IProtocol context, Message sourceMessage)
        {
            publicMemory.AddFact(CreateMessageFact(context, sourceMessage));

            var splits = new char[1] { ' ' };
            var words = sourceMessage.Text.Split(splits, StringSplitOptions.RemoveEmptyEntries);

            var notions = new List<Notion>();
            IEnumerable<Notion> matches;
            Notion notion, lastMatch;
            string nextToken;
            var matching = true;
            var source = new Source(context.ID, sourceMessage.Sender);
            var lookAhead = 0;

            for(var i=0; i<words.Length; i++)
            {
                nextToken = words[i];
                matches = privateMemory.Match(nextToken);
                matching = true;
                lookAhead = 0;
                lastMatch = null;

                // Prepare an entry for the current token
                notion = new Word(source, nextToken);

                while (matching)
                {
                    if (matches.Count() == 0)
                    {
                        if (lastMatch != null)
                        {
                            notion = lastMatch;
                            i = i + lookAhead - 1; // skip the matched word
                        }

                        matching = false; 
                    }
                    else
                    {
                        // Check if an existing notion exists
                        foreach (Notion match in matches)
                        {
                            if (match.Name == nextToken)
                            {
                                lastMatch = match;
                                break;
                            }
                        }

                        // Continue matching
                        lookAhead++;
                        if (i + lookAhead < words.Length)
                        {
                            // Create next token by joining on the next word
                            nextToken += " " + words[i + lookAhead];

                            // Prepare an entry for the new token
                            notion = new Word(source, nextToken);

                            // matches should eventually run out
                            matches = MemoryStore.Match(nextToken, matches);
                        }
                        else
                        {
                            // Create empty list of matches
                            matches = new List<Notion>();
                        }
                    }
                }

                if (lastMatch == null)
                {
                    // Add new notion
                    if (notion is Word)
                    {
                        privateMemory.AddWord((Word)notion);
                    }
                }
                notions.Add(notion);
            }

            /* Save notion as composite based on length */
            if (notions.Count == 3)
            {
                // assume the notions form a triple
                var triple = new Triple(source, notions[0], notions[1], notions[2]);
                privateMemory.AddTriples(triple);
            }
            else
            {
                // store the notions as a fact
                var fact = new Fact(source, DateTime.Now, notions);
                privateMemory.AddFact(fact);
            }
        }

        protected Fact CreateMessageFact(IProtocol context, Message sourceMessage)
        {
            Fact fact;
            Word message;
            Source source;

            source = new Source(String.Format("{0}#{1}", context.ID, sourceMessage.Domain), sourceMessage.Sender);
            message = new Word(source, sourceMessage.Text);
            fact = new Fact(source, DateTime.Now, RelatedNotions(message));

            return fact;
        }

        private List<Notion> RelatedNotions(params Notion[] args)
        {
            return new List<Notion>(args);
        }

        protected void CheckForActions(IProtocol context, Message message, Notion result)
        {
            TryAction(context, message);
        }

        protected void TryAction(IProtocol context, Message message)
        {
            if (message.Text.ToLower() == "bye")
            {
                context.Say(message.Domain, message.Sender, "I don't trust you any more");
            }
            else if (message.Text.ToLower() == "roll die")
            {
                var roll = (DateTime.Now.Second % 6) + 1;
                context.Say(message.Domain, message.Sender, "Rolled " + roll);
            }
            else if (message.Text.ToLower().IndexOf("say", 0) > -1)
            {
                var splits = new char[1] { ' ' };
                var args = message.Text.Split(splits, 3, StringSplitOptions.RemoveEmptyEntries);
                context.Say(args[1], "", args[2]);
            }
            else if (message.Text.ToLower().IndexOf("notion", 0) == 0)
            {
                var source = new Source(String.Format("{0}#{1}", context.ID, message.Domain), message.Sender);
                var notion = new Word(source, message.Text.ToLower().Substring("notion".Length).Trim());
                privateMemory.AddWord(notion);
            }
            else if (message.Text.ToLower() == "quit")
            {
                //context.Disconnect();
            }
            else if (message.Text.ToLower().IndexOf("join") == 0)
            {
                var splits = new char[1] { ' ' };
                var words = message.Text.Split(splits, StringSplitOptions.RemoveEmptyEntries);

                try
                {
                    IRCProtocol irc = (IRCProtocol)context;
                    irc.Join(words[1]);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Context was not IRC- join action failed: " + e.Message);
                }
            }
            else if (message.Text.ToLower().IndexOf("notion", 0) > -1)
            {
                var splits = new char[1] { ' ' };
                var args = message.Text.Split(splits, 2);
                privateMemory.AddNotion(new Notion(new Source(context.ID, args[1].Trim())));
                Console.WriteLine("command: notion({0}", args[1].Trim());
            }
        }

    }
}
